/**
 * Copyright (C) 2010-2016 eBusiness Information, Excilys Group
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed To in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package org.ohosannotations.api.sharedpreferences;

import ohos.data.preferences.Preferences;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Set;

/**
 * Reflection utils to call most efficient methods of SharedPreferences and
 * SharedPreferences$Editor or fall back to another implementations.
 *
 * @author dev
 * @since 2021-07-22
 */
public abstract class SharedPreferencesCompat {
    private static final HiLogLabel LABEL = new HiLogLabel(HiLog.LOG_APP, 0x01, "ohosannotation");
    private static final Method APPLY_METHOD = findMethod(Preferences.class, "flush");
    private static final Method GET_STRING_SET_METHOD =
        findMethod(Preferences.class, "getStringSet", String.class, Set.class);
    private static final Method PUT_STRING_SET_METHOD =
        findMethod(Preferences.class, "putStringSet", String.class, Set.class);

    private SharedPreferencesCompat() {
    }

    /**
     * 应用
     *
     * @param editor 编辑器
     */
    public static void apply(Preferences editor) {
        try {
            invoke(APPLY_METHOD, editor);
            return;
        } catch (NoSuchMethodException error) {
            HiLog.error(LABEL, "SharedPreferencesCompat " + error.getLocalizedMessage());
        }
    }

    /**
     * 获取字符串设置
     *
     * @param preferences 首选项
     * @param key 关键
     * @param defValues def值
     * @return {@link Set}
     */
    public static Set<String> getStringSet(Preferences preferences, String key, Set<String> defValues) {
        try {
            return invoke(GET_STRING_SET_METHOD, preferences, key, defValues);
        } catch (NoSuchMethodException e) {
            String serializedSet = preferences.getString(key, null);
            if (serializedSet == null) {
                return defValues;
            }
            return SetXmlSerializer.deserialize(serializedSet);
        }
    }

    /**
     * 把字符串设置
     *
     * @param editor 编辑器
     * @param key 关键
     * @param values 值
     */
    public static void putStringSet(Preferences editor, String key, Set<String> values) {
        try {
            invoke(PUT_STRING_SET_METHOD, editor, key, values);
        } catch (NoSuchMethodException e1) {
            editor.putString(key, SetXmlSerializer.serialize(values));
        }
    }

    /**
     * 找到方法
     *
     * @param clazz clazz
     * @param name 的名字
     * @param parameterTypes 参数类型
     * @return {@link Method}
     */
    private static Method findMethod(Class<?> clazz, String name, Class<?>... parameterTypes) {
        try {
            return clazz.getMethod(name, parameterTypes);
        } catch (NoSuchMethodException unused) {
            // fall through
        }
        return null; // 返回空
    }

    /**
     * invoke
     *
     * @param method 方法
     * @param obj obj
     * @param args arg游戏
     * @param <T> 子类泛型
     * @return method
     * @throws NoSuchMethodException 没有这样的方法异常
     */
    public static <T> T invoke(Method method, Object obj, Object... args) throws NoSuchMethodException {
        if (method == null) {
            throw new NoSuchMethodException();
        }

        try {
            return (T) method.invoke(obj, args);
        } catch (IllegalAccessException | InvocationTargetException | IllegalArgumentException e) {
            // fall through
        }
        throw new NoSuchMethodException(method.getName());
    }
}
